#include "global.h"
#include "ActorUtil.h"
#include "IniFile.h"
#include "RageFile.h"
#include "RageFileManager.h"
#include "RageLog.h"
#include "RageTypes.h"
#include "LuaManager.h"
#include "LuaBinding.h"
#include "XmlFile.h"
#include "XmlFileUtil.h"

#include <cstddef>
#include <map>
#include <set>
#include <vector>

#define TWEEN_QUEUE_MAX 50

RString unique_name();
void convert_xmls_in_dir(RString const& dirname);
void convert_xml_file(RString const& fname, RString const& dirname);
RString maybe_conv_pos(RString pos, RString (*conv_func)(float p));
RString add_extension_to_relative_path_from_found_file(
	RString const& relative_path, RString const& found_file);

RString unique_name(RString const& type)
{
	static char const* name_chars= "abcdefghijklmnopqrstuvwxyz";
	static int name_count= 0;
	int curr_name= name_count;
	RString ret= "xtl_" + type + "_"; // Minimize the chance of a name collision.
	ret= ret + name_chars[curr_name%26];
	while(curr_name / 26 > 0)
	{
		curr_name= curr_name / 26;
		ret= ret + name_chars[curr_name%26];
	}
	++name_count;
	return ret;
}

void convert_xmls_in_dir(RString const& dirname)
{
	std::vector<RString> listing;
	FILEMAN->GetDirListing(dirname, listing, false, true);
	for(std::vector<RString>::iterator curr_file= listing.begin();
		curr_file != listing.end(); ++curr_file)
	{
		switch(ActorUtil::GetFileType(*curr_file))
		{
			case FT_Xml:
				convert_xml_file(*curr_file, dirname);
				break;
			case FT_Directory:
				convert_xmls_in_dir((*curr_file) + "/");
				break;
			default: // Ignore anything not xml or directory.
				break;
		}
	}
}

RString convert_xpos(float x)
{
	return "SCREEN_CENTER_X + " + std::to_string(x - 320.0f);
}

RString convert_ypos(float y)
{
	return "SCREEN_CENTER_Y + " + std::to_string(y - 240.0f);
}

RString maybe_conv_pos(RString pos, RString (*conv_func)(float p))
{
	float f;
	if(pos >> f)
	{
		return conv_func(f);
	}
	return pos;
}

size_t after_slash_or_zero(RString const& s)
{
	size_t ret= s.rfind('/');
	if(ret != std::string::npos)
	{
		return ret+1;
	}
	return 0;
}

RString add_extension_to_relative_path_from_found_file(
	RString const& relative_path, RString const& found_file)
{
	size_t rel_last_slash= after_slash_or_zero(relative_path);
	size_t found_last_slash= after_slash_or_zero(found_file);
	return relative_path.Left(rel_last_slash) +
		found_file.substr(found_last_slash, std::string::npos);
}

bool verify_arg_count(RString cmd, std::vector<RString>& args, size_t req)
{
	if(args.size() < req)
	{
		LuaHelpers::ReportScriptError("Not enough args to " + cmd + " command.");
		return false;
	}
	return true;
}

typedef void (*arg_converter_t)(std::vector<RString>& args);

std::map<RString, arg_converter_t> arg_converters;
std::map<RString, size_t> tween_counters;
std::set<RString> fields_that_are_strings;
std::map<RString, RString> chunks_to_replace;

#define COMMON_ARG_VERIFY(count) if(!verify_arg_count(args[0], args, count)) return;
void x_conv(std::vector<RString>& args)
{
	COMMON_ARG_VERIFY(2);
	float pos;
	if(args[1] >> pos)
	{
		args[1]= convert_xpos(pos);
	}
}
void y_conv(std::vector<RString>& args)
{
	COMMON_ARG_VERIFY(2);
	float pos;
	if(args[1] >> pos)
	{
		args[1]= convert_ypos(pos);
	}
}
void string_arg_conv(std::vector<RString>& args)
{
	COMMON_ARG_VERIFY(2);
	args[1]= "\"" + args[1] + "\"";
}
void lower_string_conv(std::vector<RString>& args)
{
	args[0].MakeLower();
}
void hidden_conv(std::vector<RString>& args)
{
	COMMON_ARG_VERIFY(2);
	args[0]= "visible";
	if(args[1] == "1")
	{
		args[1]= "false";
	}
	else
	{
		args[1]= "true";
	}
}
void diffuse_conv(std::vector<RString>& args)
{
	COMMON_ARG_VERIFY(2);
	RString retarg;
	for(size_t i= 1; i < args.size(); ++i)
	{
		retarg+= args[i];
		if(i < args.size()-1)
		{
			retarg+= ",";
		}
	}
	args[1]= "color('" + retarg + "')";
	args.resize(2);
}

// Prototype for a function that is created by a macro in another translation unit and has no visible prototype, don't do this unless you have a good reason.
const RString& BlendModeToString(BlendMode);
const RString& CullModeToString(CullMode);
void blend_conv(std::vector<RString>& args)
{
	COMMON_ARG_VERIFY(2);
	for(int i= 0; i < NUM_BlendMode; ++i)
	{
		RString blend_str= BlendModeToString(static_cast<BlendMode>(i));
		blend_str.MakeLower();
		if(args[1] == blend_str)
		{
			args[1]= "\"BlendMode_" + BlendModeToString(static_cast<BlendMode>(i)) + "\"";
			break;
		}
	}
}
void cull_conv(std::vector<RString>& args)
{
	COMMON_ARG_VERIFY(2);
	for(int i= 0; i < NUM_CullMode; ++i)
	{
		RString cull_str= CullModeToString(static_cast<CullMode>(i));
		cull_str.MakeLower();
		if(args[1] == cull_str)
		{
			args[1]= "\"CullMode_" + CullModeToString(static_cast<CullMode>(i)) + "\"";
			break;
		}
	}
}
#undef COMMON_ARG_VERIFY

void init_parser_helpers()
{
	arg_converters["x"]= &x_conv;
	arg_converters["y"]= &y_conv;
	arg_converters["queuecommand"]= &string_arg_conv;
	arg_converters["playcommand"]= &string_arg_conv;
	arg_converters["effectclock"]= &string_arg_conv;
	arg_converters["EffectMagnitude"]= &lower_string_conv;
	arg_converters["ZoomToWidth"]= &lower_string_conv;
	arg_converters["ZoomToHeight"]= &lower_string_conv;
	arg_converters["blend"]= &blend_conv;
	arg_converters["cullmode"]= &cull_conv;
	arg_converters["hidden"]= &hidden_conv;
	arg_converters["diffuse"]= &diffuse_conv;
	arg_converters["effectcolor1"]= &diffuse_conv;
	arg_converters["effectcolor2"]= &diffuse_conv;
	tween_counters["sleep"]= 2;
	tween_counters["linear"]= 1;
	tween_counters["accelerate"]= 1;
	tween_counters["decelerate"]= 1;
	tween_counters["spring"]= 1;
	tween_counters["tween"]= 1;
	tween_counters["queuecommand"]= 1;
	fields_that_are_strings.insert("Texture");
	fields_that_are_strings.insert("Text");
	fields_that_are_strings.insert("AltText");
	fields_that_are_strings.insert("File");
	fields_that_are_strings.insert("Font");
	fields_that_are_strings.insert("Meshes");
	fields_that_are_strings.insert("Materials");
	fields_that_are_strings.insert("Bones");
	chunks_to_replace["hidden(0)"]= "visible(true)";
	chunks_to_replace["hidden(1)"]= "visible(false)";
	chunks_to_replace["effectdelay"]= "effect_hold_at_full";
	chunks_to_replace["IsPlayerEnabled(0)"]= "IsPlayerEnabled(PLAYER_1)";
	chunks_to_replace["IsPlayerEnabled(1)"]= "IsPlayerEnabled(PLAYER_2)";
}

void convert_lua_chunk(RString& chunk_text)
{
	for(std::map<RString, RString>::iterator chunk= chunks_to_replace.begin();
			chunk != chunks_to_replace.end(); ++chunk)
	{
		chunk_text.Replace(chunk->first.c_str(), chunk->second.c_str());
	}
}

// Conditions are mapped by condition string.
// So condition_set_t::iterator->first is the lua to execute for the
// condition, and condition_set_t::iterator->second is the name of the
// condition.
typedef std::map<RString, RString> condition_set_t;
typedef std::map<RString, RString> field_cont_t;
struct frame_t
{
	int frame;
	float delay;
	frame_t() :frame(0), delay(0.0f) {}
};

struct actor_template_t
{
	RString type;
	field_cont_t fields;
	RString condition;
	RString name;
	std::vector<frame_t> frames;
	std::vector<actor_template_t> children;
	RString x;
	RString y;
	void make_space_for_frame(int id);
	void store_cmd(RString const& cmd_name, RString const& full_cmd);
	void store_field(RString const& field_name, RString const& value, bool cmd_convert, RString const& pref= "", RString const& suf= "");
	void store_field(RString const& field_name, XNodeValue const* value, bool cmd_convert, RString const& pref= "", RString const& suf= "");
	void rename_field(RString const& old_name, RString const& new_name);
	RString get_field(RString const& field_name);
	void load_frames_from_file(RString const& fname, RString const& rel_path);
	void load_model_from_file(RString const& fname, RString const& rel_path);
	void load_node(XNode const& node, RString const& dirname, condition_set_t& conditions);
	void output_to_file(RageFile* file, RString const& indent);
};

void actor_template_t::make_space_for_frame(int id)
{
	if(id >= static_cast<int>(frames.size()))
	{
		frames.resize(id+1);
	}
}

void actor_template_t::store_cmd(RString const& cmd_name, RString const& full_cmd)
{
	if(full_cmd.Left(1) == "%")
	{
		RString cmd_text= full_cmd.Right(full_cmd.size()-1);
		convert_lua_chunk(cmd_text);
		fields[cmd_name]= cmd_text;
		return;
	}
	std::vector<RString> cmds;
	split(full_cmd, ";", cmds, true);
	size_t queue_size= 0;
	// If someone has a simfile that uses a playcommand that pushes tween
	// states onto the queue, queue size counting will have to be made much
	// more complex to prevent that from causing an overflow.
	for(std::vector<RString>::iterator cmd= cmds.begin(); cmd != cmds.end(); ++cmd)
	{
		std::vector<RString> args;
		split(*cmd, ",", args, true);
		if(!args.empty())
		{
			for(std::vector<RString>::iterator arg= args.begin(); arg != args.end(); ++arg)
			{
				size_t first_nonspace= 0;
				size_t last_nonspace= arg->size();
				while((*arg)[first_nonspace] == ' ')
				{
					++first_nonspace;
				}
				while((*arg)[last_nonspace] == ' ')
				{
					--last_nonspace;
				}
				*arg= arg->substr(first_nonspace, last_nonspace - first_nonspace);
			}
			std::map<RString, arg_converter_t>::iterator conv= arg_converters.find(args[0]);
			if(conv != arg_converters.end())
			{
				conv->second(args);
			}
			std::map<RString, size_t>::iterator counter= tween_counters.find(args[0]);
			if(counter != tween_counters.end())
			{
				queue_size+= counter->second;
			}
		}
		*cmd= join(",", args);
	}
	// This code is probably actually useless, OITG has the same tween queue size
	// and the real reason I saw overflows in converted files was a bug in
	// foreground loading that ran InitCommand twice. -Kyz
	if(queue_size >= TWEEN_QUEUE_MAX)
	{
		size_t num_to_make= (queue_size / TWEEN_QUEUE_MAX) + 1;
		size_t states_per= (queue_size / num_to_make) + 1;
		size_t states_in_curr= 0;
		RString this_name= cmd_name;
		std::vector<RString> curr_cmd;
		for(std::vector<RString>::iterator cmd= cmds.begin(); cmd != cmds.end(); ++cmd)
		{
			curr_cmd.push_back(*cmd);
			std::vector<RString> args;
			split(*cmd, ",", args, true);
			if(!args.empty())
			{
				std::map<RString, size_t>::iterator counter= tween_counters.find(args[0]);
				if(counter != tween_counters.end())
				{
					states_in_curr+= counter->second;
					if(states_in_curr >= states_per - 1)
					{
						RString next_name= unique_name("cmd");
						curr_cmd.push_back("queuecommand,\"" + next_name + "\"");
						fields[this_name]= "cmd(" + join(";", curr_cmd) + ")";
						curr_cmd.clear();
						this_name= next_name;
						states_in_curr= 0;
					}
				}
			}
		}
		if(!curr_cmd.empty())
		{
			fields[this_name]= "cmd(" + join(";", curr_cmd) + ")";
		}
	}
	else
	{
		fields[cmd_name]= "cmd(" + join(";", cmds) + ")";
	}
}

void actor_template_t::store_field(RString const& field_name, RString const& value, bool cmd_convert, RString const& pref, RString const& suf)
{
	// OITG apparently allowed "Oncommand" as valid.
	if(field_name.Right(7).MakeLower() != "command")
	{
		cmd_convert= false;
	}
	if(cmd_convert)
	{
		RString real_field_name= field_name.Left(field_name.size()-7) + "Command";
		store_cmd(real_field_name, value);
	}
	else
	{
		fields[field_name]= pref + value + suf;
	}

}
void actor_template_t::store_field(RString const& field_name, XNodeValue const* value, bool cmd_convert, RString const& pref, RString const& suf)
{
	RString val;
	value->GetValue(val);
	store_field(field_name, val, cmd_convert, pref, suf);
}

void actor_template_t::rename_field(RString const& old_name, RString const& new_name)
{
	field_cont_t::iterator old_field= fields.find(old_name);
	if(old_field == fields.end())
	{
		return;
	}
	fields[new_name]= old_field->second;
	fields.erase(old_field);
}

RString actor_template_t::get_field(RString const& field_name)
{
	field_cont_t::iterator field= fields.find(field_name);
	if(field == fields.end())
	{
		return "";
	}
	return field->second;
}

void actor_template_t::load_frames_from_file(RString const& fname, RString const& rel_path)
{
	IniFile ini;
	if(!ini.ReadFile(fname))
	{
		LOG->Trace("Failed to read sprite file %s: %s", fname.c_str(), ini.GetError().c_str());
		return;
	}
	XNode const* sprite_node= ini.GetChild("Sprite");
	if(sprite_node != nullptr)
	{
		FOREACH_CONST_Attr(sprite_node, attr)
		{
			// Frame and Delay fields have names of the form "Frame0000" where the
			// "0000" part is the id of the frame.
			RString field_type= attr->first.Left(5);
			if(field_type == "Frame")
			{
				int id= StringToInt(attr->first.Right(attr->first.size()-5));
				make_space_for_frame(id);
				attr->second->GetValue(frames[id].frame);
			}
			else if(field_type == "Delay")
			{
				int id= StringToInt(attr->first.Right(attr->first.size()-5));
				make_space_for_frame(id);
				attr->second->GetValue(frames[id].delay);
			}
			else if(field_type == "Textu")
			{
				store_field("Texture", attr->second, false, rel_path, "");
			}
			else
			{
				// Unrecognized, ignore.
			}
		}
	}
}

void actor_template_t::load_model_from_file(RString const& fname, RString const& rel_path)
{
	IniFile ini;
	if(!ini.ReadFile(fname))
	{
		LOG->Trace("Failed to read model file %s: %s", fname.c_str(), ini.GetError().c_str());
		return;
	}
	XNode const* model_node= ini.GetChild("Model");
	if(model_node != nullptr)
	{
		FOREACH_CONST_Attr(model_node, attr)
		{
			store_field(attr->first, attr->second, false, rel_path, "");
		}
	}
}

void actor_template_t::load_node(XNode const& node, RString const& dirname, condition_set_t& conditions)
{
	type= node.GetName();
	bool type_set_by_automagic= false;
#define set_type(auto_type) type_set_by_automagic= true; type= auto_type;
	FOREACH_CONST_Attr(&node, attr)
	{
		if(attr->first == "Name")
		{
			attr->second->GetValue(name);
		}
		else if(attr->first == "Condition")
		{
			RString cond_str;
			attr->second->GetValue(cond_str);
			condition_set_t::iterator cond= conditions.find(cond_str);
			if(cond == conditions.end())
			{
				condition= unique_name("cond");
				conditions[cond_str]= condition;
			}
			else
			{
				condition= cond->second;
			}
		}
		else if(attr->first == "Type" || attr->first == "Class")
		{
			if(!type_set_by_automagic)
			{
				attr->second->GetValue(type);
			}
		}
		else if(attr->first == "__TEXT__")
		{
			// Ignore.  This attribute seems to be put in by the xml parser, and
			// not part of the actual xml code.
		}
		else if(attr->first == "Text")
		{
			set_type("BitmapText");
			store_field(attr->first, attr->second, true);
		}
		else if(attr->first == "File")
		{
			RString relative_path;
			attr->second->GetValue(relative_path);
			RString sfname= dirname + relative_path;
			if(FILEMAN->IsADirectory(sfname))
			{
				set_type("LoadActor");
				store_field("File", attr->second, false);
			}
			else
			{
				std::vector<RString> files_in_dir;
				FILEMAN->GetDirListing(sfname + "*", files_in_dir, false, true);
				int handled_level= 0;
				RString found_file= "";
				for(std::vector<RString>::iterator file= files_in_dir.begin();
						file != files_in_dir.end() && handled_level < 2; ++file)
				{
					RString extension= GetExtension(*file);
					FileType file_type= ActorUtil::GetFileType(*file);
					RString this_relative=
						add_extension_to_relative_path_from_found_file(relative_path, *file);
					switch(file_type)
					{
						case FT_Xml:
							this_relative= SetExtension(this_relative, "lua");
							[[fallthrough]];
						case FT_Lua:
							set_type("LoadActor");
							store_field("File", this_relative, false);
							handled_level= 2;
							break;
						case FT_Ini:
							break;
						case FT_Bitmap:
						case FT_Movie:
							if(handled_level < 2)
							{
								set_type("Sprite");
								store_field("File", this_relative, false);
								handled_level= 1;
							}
							break;
						case FT_Sound:
							set_type("ActorSound");
							store_field("File", this_relative, false);
							handled_level= 1;
							break;
						case FT_Sprite:
							set_type("Sprite");
							load_frames_from_file(dirname + this_relative, Dirname(relative_path));
							handled_level= 2;
							break;
						case FT_Model:
							set_type("Model");
							store_field("Meshes", this_relative, false);
							store_field("Materials", this_relative, false);
							store_field("Bones", this_relative, false);
							handled_level= 2;
							break;
						default:
							break;
					}
					if(!handled_level)
					{
						if(extension == "model")
						{
							set_type("Model");
							load_model_from_file(dirname + this_relative, Dirname(relative_path));
							handled_level= 2;
						}
					}
				}
				if(!handled_level)
				{
					if(!files_in_dir.empty())
					{
						RString this_relative=
							add_extension_to_relative_path_from_found_file(relative_path, files_in_dir[0]);
						store_field("File", this_relative, false);
					}
					else
					{
						store_field("File", attr->second, false);
					}
				}
			}
		}
		else
		{
			store_field(attr->first, attr->second, true);
		}
	}
	if(type == "BitmapText")
	{
		rename_field("Texture", "Font");
		rename_field("File", "Font");
	}
	if(type == "Sprite")
	{
		rename_field("File", "Texture");
	}
	XNode const* xren= node.GetChild("children");
	if(xren != nullptr)
	{
		FOREACH_CONST_Child(xren, child)
		{
			actor_template_t chill_plate;
			chill_plate.load_node(*child, dirname, conditions);
			children.push_back(chill_plate);
		}
	}
	if(!x.empty() || !y.empty())
	{
		RString pos_init= "xy," + x + "," + y;
		field_cont_t::iterator init= fields.find("InitCommand");
		if(init != fields.end())
		{
			pos_init= pos_init + ";queuecommand,xtl_passed_initCommand";
			fields["xtl_passed_initCommand"]= init->second;
		}
		fields["InitCommand"]= "cmd(" + pos_init + ")";
	}
}

void actor_template_t::output_to_file(RageFile* file, RString const& indent)
{
	if(!condition.empty())
	{
		file->Write(indent + "optional_actor(" + condition + "_result,\n");
	}
	if(type == "LoadActor")
	{
		file->Write(indent + "LoadActor('" + get_field("File") + "') .. {\n");
	}
	else
	{
		file->Write(indent + "Def." + type + "{\n");
	}
	RString subindent= indent + "  ";
	if(name.empty())
	{
		name= unique_name("actor");
	}
	file->Write(subindent + "Name= \"" + name + "\",\n");
	if(!frames.empty())
	{
		file->Write(subindent + "Frames= {\n");
		RString frameindent= subindent + "  ";
		for(std::vector<frame_t>::iterator frame= frames.begin();
			frame != frames.end(); ++frame)
		{
			file->Write(frameindent + "{Frame= " + std::to_string(frame->frame) +
				", Delay= " + std::to_string(frame->delay) + "},\n");
		}
		file->Write(indent + "},\n");
	}
	for(field_cont_t::iterator field= fields.begin();
		field != fields.end(); ++field)
	{
		std::set<RString>::iterator is_string= fields_that_are_strings.find(field->first);
		if(is_string != fields_that_are_strings.end())
		{
			file->Write(subindent + field->first + "= \"" + field->second + "\",\n");
		}
		else
		{
			file->Write(subindent + field->first + "= " + field->second + ",\n");
		}
	}
	for(std::vector<actor_template_t>::iterator child= children.begin();
		child != children.end(); ++child)
	{
		child->output_to_file(file, subindent);
		file->Write(",\n");
	}
	file->Write(indent + "}");
	if(!condition.empty())
	{
		file->Write(")");
	}
}

void convert_xml_file(RString const& fname, RString const& dirname)
{
	if(arg_converters.empty())
	{
		init_parser_helpers();
	}
	LOG->Trace("Beginning conversion of entry: %s", fname.c_str());
	XNode xml;
	if(!XmlFileUtil::LoadFromFileShowErrors(xml, fname))
	{
		LOG->Trace("Error when loading xml.");
		return;
	}
	actor_template_t plate;
	condition_set_t conditions;
	plate.load_node(xml, dirname, conditions);
	RageFile* file= new RageFile;
	RString out_name= fname.Left(fname.size()-4) + ".lua";
	if(!file->Open(out_name, RageFile::WRITE))
	{
		LOG->Trace("Could not open %s: %s", out_name.c_str(), file->GetError().c_str());
		return;
	}
	LOG->Trace("Saving conversion to: %s", out_name.c_str());
	for(condition_set_t::iterator cond= conditions.begin();
		cond != conditions.end(); ++cond)
	{
		RString cond_text= cond->first;
		convert_lua_chunk(cond_text);
		file->Write("local " + cond->second + "_result= " + cond_text + "\n\n");
	}
	if(!conditions.empty())
	{
		file->Write("local function optional_actor(cond, actor)\n"
			"	if cond then return actor end\n"
			"	return Def.Actor{}\n"
			"end\n\n");
	}
	file->Write("return ");
	plate.output_to_file(file, "");
	file->Write("\n");
	file->Close();
	delete file;
}

int LuaFunc_convert_xml_bgs(lua_State* L);
int LuaFunc_convert_xml_bgs(lua_State* L)
{
	RString dir= SArg(1);
	std::vector<RString> xml_list;
	convert_xmls_in_dir(dir + "/");
	return 0;
}

LUAFUNC_REGISTER_COMMON(convert_xml_bgs);

/*
 * (c) 2014 Eric Reese
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
